#include "FrameBufferObject.h"
#include "TextureGLES.h"
#include "RenderTextureGLES.h"
#include "RendererGLES.h"

namespace Nezha
{
	FrameBufferObject::FrameBufferObject(u16 numSamples)
		:mNumSamples(numSamples)
		,mFBO_GLES(0)
		,mMultisampleFBO_GLES(0)
	{
	}

	FrameBufferObject::~FrameBufferObject()
	{
		// release all the attachment
		mDepthBuffer.release();

		mMultisampleColorBuffer.mRenderBuffer.release();
		mMultisampleColorBuffer.mTexture.release();

		for(int i = 0; i < NEZHA_MAX_MULTIPLE_RENDER_TARGETS; i++)
		{
			mColorBuffers[i].mRenderBuffer.release();
			mColorBuffers[i].mTexture.release();
		}

		glDeleteFramebuffers(1, &mFBO_GLES);
		GL_ERROR_DUMP(GLESAdaptor::GetInstance());

		if(mMultisampleFBO_GLES)
		{
			glDeleteFramebuffers(1, &mMultisampleFBO_GLES);
			GL_ERROR_DUMP(GLESAdaptor::GetInstance());
		}
	}

	bool FrameBufferObject::initializeOnce()
	{
		if(mFBO_GLES)
		{
			return false;
		}

		glGenFramebuffers(1, &mFBO_GLES);
		GL_ERROR_DUMP(GLESAdaptor::GetInstance());

		GLint maxSamples = 0;

#if GL_APPLE_framebuffer_multisample

		glBindFramebuffer(GL_FRAMEBUFFER, mFBO_GLES);
		glGetIntegerv(GL_MAX_SAMPLES_APPLE, &maxSamples);
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		mNumSamples = std::min(mNumSamples, (GLsizei)maxSamples);

#endif

		if(mNumSamples > 0)
		{
			glGenFramebuffers(1, &mMultisampleFBO_GLES);
		}

		return true;
	}

	void FrameBufferObject::bind()
	{
		glBindFramebuffer(GL_FRAMEBUFFER, mMultisampleFBO_GLES ? mMultisampleFBO_GLES : mFBO_GLES);
		GL_ERROR_DUMP(GLESAdaptor::GetInstance());
	}

	bool FrameBufferObject::attachColorBuffer(u16 attachment, FBOAttachment& fboa)
	{
		if(fboa.mBufferType == FBOAttachment::AT_NONE)
		{
			return false;
		}

		mColorBuffers[attachment] = fboa;

		glBindFramebuffer(GL_FRAMEBUFFER, mFBO_GLES);
		GL_ERROR_DUMP(GLESAdaptor::GetInstance());

		GLenum index = GL_COLOR_ATTACHMENT0 + attachment;

		switch(fboa.mBufferType)
		{
		case FBOAttachment::AT_RENDER_BUFFER:
			{
				glFramebufferRenderbuffer(GL_FRAMEBUFFER, index, GL_RENDERBUFFER, fboa.mRenderBuffer->getGLHandle());
				GL_ERROR_DUMP(GLESAdaptor::GetInstance());
			}
			break;
		case FBOAttachment::AT_TEXTURE:
			{
				// FIXME texture2d only now.
				TextureGLES* tex = static_cast<TextureGLES*>(fboa.mTexture.get());
				glFramebufferTexture2D(GL_FRAMEBUFFER, index, GL_TEXTURE_2D, tex->getGLHandle(), 0);
				GL_ERROR_DUMP(GLESAdaptor::GetInstance());
			}
			break;
		}

		return true;
	}

	void FrameBufferObject::detachColorBuffer(u16 attachment)
	{
		glBindFramebuffer(GL_FRAMEBUFFER, mFBO_GLES);
		GL_ERROR_DUMP(GLESAdaptor::GetInstance());

		GLenum index = GL_COLOR_ATTACHMENT0 + attachment;

		switch(mColorBuffers[attachment].mBufferType)
		{
		case FBOAttachment::AT_RENDER_BUFFER:
			{
				glFramebufferRenderbuffer(GL_FRAMEBUFFER, index, GL_RENDERBUFFER, 0);
				GL_ERROR_DUMP(GLESAdaptor::GetInstance());
			}
			break;
		case FBOAttachment::AT_TEXTURE:
			{
				// FIXME texture2d only now.
				glFramebufferTexture2D(GL_FRAMEBUFFER, index, GL_TEXTURE_2D, 0, 0);
				GL_ERROR_DUMP(GLESAdaptor::GetInstance());
			}
			break;
		}
	}

	bool FrameBufferObject::attachDepthBuffer(DepthBufferGLESPtr& db)
	{
		glBindFramebuffer(GL_FRAMEBUFFER, mMultisampleFBO_GLES ? mMultisampleFBO_GLES : mFBO_GLES);
		GL_ERROR_DUMP(GLESAdaptor::GetInstance());

		if(db.notNull())
		{
			RenderBufferGLES* depthBuf = db->getDepthBuffer();
			RenderBufferGLES* stencilBuf = db->getStencilBuffer();

			if(depthBuf)
				depthBuf->bind2FrameBuffer(GL_DEPTH_ATTACHMENT);

			if(stencilBuf)
				stencilBuf->bind2FrameBuffer(GL_STENCIL_ATTACHMENT);
		}
		else
		{
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
			GL_ERROR_DUMP(GLESAdaptor::GetInstance());
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
			GL_ERROR_DUMP(GLESAdaptor::GetInstance());
		}

		mDepthBuffer = db;

		return true;
	}

	void FrameBufferObject::detachDepthBuffer()
	{
		glBindFramebuffer(GL_FRAMEBUFFER, mMultisampleFBO_GLES ? mMultisampleFBO_GLES : mFBO_GLES);
		GL_ERROR_DUMP(GLESAdaptor::GetInstance());

		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
		GL_ERROR_DUMP(GLESAdaptor::GetInstance());
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
		GL_ERROR_DUMP(GLESAdaptor::GetInstance());

		mDepthBuffer.release();
	}



	FBORTTManager::FBORTTManager()
	{

	}

	FBORTTManager::~FBORTTManager()
	{
		destroyAll();
	}

	void FBORTTManager::setCurrent(RenderTarget* rt)
	{
		if(rt)
		{
			RenderTextureGLES* rt_gles = static_cast<RenderTextureGLES*>(rt);
			FrameBufferObject* fbo = rt_gles->getFBO();
			fbo->bind();
		}
		else
		{
#ifdef NZOS_IOS

			glBindFramebuffer(GL_FRAMEBUFFER, 1);

#else

			glBindFramebuffer(GL_FRAMEBUFFER, 0);

#endif
		}
	}

	RenderBufferGLESPtr FBORTTManager::createRenderBuffer(HardwareBuffer::Usage usage, u16 w, u16 h, u16 numSamples, u16 bitDepth)
	{
		RenderBufferGLESPtr rb;

		// FIXME we share renderbuffer now.

		GLenum format = RenderBufferGLES::Usage2GLFormat(usage, bitDepth);

		if(format != GL_NONE)
		{
			RenderBufferDesc desc(format, w, h, numSamples);
			RenderBufferMap::iterator it = mRenderBuffers.find(desc);

			if(it != mRenderBuffers.end())
			{
				return it->second;
			}
			else
			{
				RenderBufferGLES* rb_gles = NZ_New RenderBufferGLES(usage, w, h, numSamples);
				if(!rb_gles->_initializeOnce(format, bitDepth))
				{
					NZ_Delete rb_gles;
					return rb;
				}

				rb = rb_gles;
				mRenderBuffers.insert(MapPair<RenderBufferDesc, RenderBufferGLESPtr>(desc, rb));
			}
		}

		return rb;
	}

	RenderTarget* FBORTTManager::createRenderTarget(u16 w, u16 h, u16 aa, TexFormat fmt, bool renderTexture)
	{
		u16 bits = TextureManager::TexFormatBits(fmt);

		RenderTextureGLES* rt = NZ_New RenderTextureGLES(w, h, aa, bits);

		if(!rt->_initializeOnce(this, fmt, renderTexture))
		{
			NZ_Delete rt;
			rt = NULL;
		}

		mRenderTargets.push_back(rt);

		return static_cast<RenderTarget*>(rt);
	}

	void FBORTTManager::destroyRenderTarget(RenderTarget* rt)
	{
		RenderTargetList::iterator it = find(mRenderTargets.begin(), mRenderTargets.end(), rt);

		if(it != mRenderTargets.end())
		{
			NZ_Delete (*it);
			mRenderTargets.erase(it);
		}
	}

	void FBORTTManager::_garbageDispose()
	{
		for(RenderBufferMap::iterator it = mRenderBuffers.begin(); it != mRenderBuffers.end(); )
		{
			if(it->second->getRefCount() == 1)
			{
				it->second.release();
				it = mRenderBuffers.erase(it);
			}
			else
			{
				it++;
			}
		}

		for(DepthBufferList::iterator it = mDepthBuffers.begin(); it != mDepthBuffers.end(); )
		{
			if((*it)->getRefCount() == 1)
			{
				(*it).release();
				it = mDepthBuffers.erase(it);
			}
			else
			{
				it++;
			}
		}
	}

	void FBORTTManager::destroyAll()
	{
		for(RenderBufferMap::iterator it = mRenderBuffers.begin(); it != mRenderBuffers.end(); it++)
		{
			it->second.release();
		}

		for(DepthBufferList::iterator it = mDepthBuffers.begin(); it != mDepthBuffers.end(); it++)
		{
			(*it).release();
		}

		for(RenderTargetList::iterator it = mRenderTargets.begin(); it != mRenderTargets.end(); it++)
		{
			NZ_Delete (*it);
		}

		mRenderBuffers.clear();
		mDepthBuffers.clear();
		mRenderTargets.clear();
	}

	DepthStencilBufferPtr FBORTTManager::createDepthStencilBuffer(u16 w, u16 h, u16 aa, u8 bit)
	{
		DepthStencilBufferPtr dbptr;
		RendererGLES* r = static_cast<RendererGLES*>(Renderer::GetGlobal());
		u16 dfs = r->getDepthFormatSupported();

		RenderBufferGLESPtr depthBuf = createRenderBuffer((dfs & RendererGLES::DEPTH_PACKED_SEPTH_STENCIL) ? HardwareBuffer::HBU_DEPTHSTENCIL : HardwareBuffer::HBU_DEPTH, w, h, aa, bit);
		RenderBufferGLESPtr stencilBuf;

		if(!(dfs & RendererGLES::DEPTH_PACKED_SEPTH_STENCIL))
		{
			stencilBuf = createRenderBuffer(HardwareBuffer::HBU_STENCIL, w, h, aa, bit);
		}

		DepthBufferGLES* dbp = NZ_New DepthBufferGLES(depthBuf.get(), stencilBuf.get(), w, h, aa);

		dbptr = static_cast<DepthStencilBuffer*>(dbp);
		mDepthBuffers.push_back(dbptr);

		return dbptr;
	}
}